//  
//  Copyright © 2009 Jiří Zárevúcky <zarevucky.jiri@gmail.com>
// 
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
// 
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
// 
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
// 

using System;
using System.Collections.Generic;
using Gtk;

using L=Galaxium.Protocol.Xmpp.Library;
using Galaxium.Protocol.Xmpp.Library;
using Galaxium.Protocol.Xmpp.Library.Extensions;

namespace Galaxium.Protocol.Xmpp.GtkGui
{
	public class DataFieldWidget: Alignment
	{
		private L.Client _client;
		
		public DataFieldWidget (L.Client client, DataField field)
			:base (0, 0, 1, 1)
		{
			_client = client;
			Field = field;

			switch (field.Type) {
			case DataFieldType.Boolean:     SetupCheckButton (); break;
			case DataFieldType.JidMulti:    SetupJidList (); break;
			case DataFieldType.JidSingle:   SetupJidEntry (); break;
			case DataFieldType.ListMulti:   SetupList (); break;
			case DataFieldType.ListSingle:  SetupCombo (); break;
			case DataFieldType.TextMulti:   SetupText (); break;
			case DataFieldType.TextPrivate: SetupPrivateEntry (); break;
			case DataFieldType.TextSingle:  SetupEntry (); break;

			default: throw new ArgumentException ();
			}
		}

		private System.Action _set_action;
		private System.Action _clear_action;

		private void SetupCheckButton ()
		{
			var button = new CheckButton (Field.Label ?? Field.Identifier);
			button.TooltipText = Field.Description;
			button.Active = Field.GetBool ();
			button.Toggled += (s, e) => _set_action ();
			_set_action = () => Field.SetBool (button.Active);
			_clear_action = () => button.Active = false;
			this.Add (button);
		}

		private void SetupJidEntry ()
		{
			var entry = new Entry (Field.GetJid () ?? String.Empty);
			entry.TooltipText = Field.Description;
			entry.FocusOutEvent += (s, e) => _set_action ();
			_set_action = () => {
				try { Field.SetJid (entry.Text); }
				catch { entry.Text = "Invalid JID!!"; } // TODO: better error display
			};
			_clear_action = () => entry.Text = String.Empty;
			this.Add (entry);
		}

		private void SetupJidList ()
		{
			var list_store = new ListStore (typeof (String), typeof (JabberID));
			var list = new TreeView (list_store);
			list.AppendColumn (String.Empty, new CellRendererText (), "text", 0);
			list.HeadersVisible = false;

			var values = Field.GetJids ();
			foreach (var val in values) {
				var name = _client.Roster.ContainsContact (val)
					? _client.Roster.GetContact (val).Name ?? val : (string) val;
				list_store.AppendValues (name, val);
			}

			var scroll = new ScrolledWindow ();
			scroll.Add (list);
			scroll.ShadowType = ShadowType.In;
			scroll.TooltipText = Field.Description;

			_set_action = () => {
				var jids = new List<JabberID> ();
				list_store.Foreach ((model, path, iter) => {
					jids.Add (model.GetValue (iter, 1) as JabberID);
					return false;
				});
				Field.SetJids (jids);
			};
			
			var add_btn = new Button ();
			add_btn.Add (new Image (Stock.Add, IconSize.Button));
			add_btn.Clicked += delegate (object sender, EventArgs e) {
			//	foreach (var contact in ContactSelectionDialog.Collect (_client))
			//		list_store.AppendValues (contact.Name, contact.Jid);
			//	_set_action ();
			};

			var rmv_btn = new Button ();
			rmv_btn.Add (new Image (Stock.Remove, IconSize.Button));
			rmv_btn.Clicked += delegate (object sender, EventArgs e) {
				foreach (var path in list.Selection.GetSelectedRows ()) {
					TreeIter iter;
					if (list_store.GetIter (out iter, path)) list_store.Remove (ref iter);
				}
				_set_action ();
			};

			_clear_action = () => list_store.Clear ();
			
			var table = new Table (3, 2, false);
			table.Attach (scroll, 0, 1, 0, 3);
			table.Attach (add_btn, 1, 2, 0, 1, 0, 0, 0, 0);
			table.Attach (rmv_btn, 1, 2, 1, 2, 0, 0, 0, 0);
			this.Add (table);
		}

		private void SetupList ()
		{
			var list_store = new ListStore (typeof (Boolean), typeof (String), typeof (String));
			var list = new TreeView (list_store);
			list.AppendColumn (String.Empty, new CellRendererToggle (), "active", 0);
			list.AppendColumn (String.Empty, new CellRendererText (), "text", 1);
			list.HeadersVisible = false;

			var scroll = new ScrolledWindow ();
			scroll.Add (list);
			scroll.ShadowType = ShadowType.In;
			scroll.TooltipText = Field.Description;

			var dict = new Dictionary<string, TreeIter> ();
			
			foreach (var option in Field.GetOptions ())
				dict [option.Value] =
					list_store.AppendValues (false, option.Label ?? option.Value, option.Value);

			foreach (var val in Field.GetStrings ())
				list_store.SetValue (dict [val], 0, true);

			list.ButtonPressEvent += HandleListPress;

			_set_action = () => {
				var strings = new List<string> ();
				list.Model.Foreach ((model, pth, iter) => {
					if (((bool) model.GetValue (iter, 0)) == true)
						strings.Add ((string) model.GetValue (iter, 2));
					return false;
				});
			
				Field.SetStrings (strings);
			};
			
			_clear_action = () =>
				list.Model.Foreach ((model, pth, iter) => {
					model.SetValue (iter, 0, false);
					return false;
				});
			
			this.Add (scroll);
		}

		private void HandleListPress (object o, ButtonPressEventArgs args)
		{
			TreePath path;
			if ((o as TreeView).GetPathAtPos ((int) args.Event.X, (int) args.Event.Y, out path)) {
				var model = (o as TreeView).Model;
				TreeIter iter;
				if (model.GetIter (out iter, path))
					model.SetValue (iter, 0, !(bool) model.GetValue (iter, 0));
			}

			_set_action ();
		}

		private void SetupCombo ()
		{
			var list = new ListStore (typeof (String), typeof (String));
			var combo = new ComboBox (list);
			var cell = new CellRendererText ();
			combo.PackStart (cell, true);
			combo.AddAttribute (cell, "text", 0);
			combo.TooltipText = Field.Description;

			var i = 0;
			var deflt = Field.GetString ();
			foreach (var option in Field.GetOptions ()) {
				list.AppendValues (option.Label ?? option.Value, option.Value);
				if (option.Value == deflt)
					combo.Active = i;
				i ++;
			}
			
			combo.Changed += delegate (object sender, EventArgs e) {
				TreeIter iter;
				if ((sender as ComboBox).GetActiveIter (out iter))
					Field.SetString ((sender as ComboBox).Model.GetValue (iter, 1) as string);
			};

			_set_action = _clear_action = delegate {}; // TODO: reset to default value
			
			this.Add (combo);
		}

		private void SetupText ()
		{
			var text = new TextView ();
			text.Buffer.Text = Field.GetString () ?? String.Empty;
			text.TooltipText = Field.Description;
			text.FocusOutEvent += (s, e) => _set_action ();
			text.WrapMode = WrapMode.WordChar;
			
			_set_action = () => Field.SetString (text.Buffer.Text);
			_clear_action = () => text.Buffer.Clear ();

			var scroll = new ScrolledWindow ();
			scroll.ShadowType = ShadowType.In;
			scroll.HscrollbarPolicy = PolicyType.Never;
			scroll.Add (text);
			
			this.Add (scroll);
		}

		private void SetupEntry ()
		{
			var entry = new Entry (Field.GetString () ?? String.Empty);
			entry.TooltipText = Field.Description;
			entry.FocusOutEvent += (s, e) => _set_action ();
			
			_set_action = () => Field.SetString (entry.Text);
			_clear_action = () => entry.Text = String.Empty;
			this.Add (entry);
		}

		private void SetupPrivateEntry ()
		{
			var entry = new Entry (Field.GetString () ?? String.Empty);
			entry.TooltipText = Field.Description;
			entry.Visibility = false;
			entry.FocusOutEvent += (s, e) => _set_action ();
			
			_set_action = () => Field.SetString (entry.Text);
			_clear_action = () => entry.Text = String.Empty;
			this.Add (entry);
		}

		public DataField Field { get; private set; }
		
		public void Clear ()
		{
			_clear_action.Invoke ();
			_set_action.Invoke ();
		}
	}
}
